#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Kotlin混淆器
专门处理Kotlin代码的混淆
"""

import os
import re
from typing import Dict, List, Set, Tuple
from ..builtin_config_module.builtin_config import BuiltInConfig
from ..utils_module.logger import ObfuscationLogger

class KotlinObfuscator:
    """Kotlin混淆器"""
    
    def __init__(self, config: BuiltInConfig, logger: ObfuscationLogger):
        self.config = config
        self.logger = logger
        
        # Kotlin语法模式
        self.patterns = {
            'class': r'\b(?:class|data\s+class|sealed\s+class|enum\s+class)\s+(\w+)',
            'interface': r'\binterface\s+(\w+)',
            'object': r'\bobject\s+(\w+)',
            'function': r'\bfun\s+(\w+)',
            'property': r'\b(?:val|var)\s+(\w+)',
            'constant': r'\bconst\s+val\s+(\w+)',
            'companion': r'\bcompanion\s+object\s+(\w+)?',
            'typealias': r'\btypealias\s+(\w+)',
            'annotation': r'\bannotation\s+class\s+(\w+)',
            'extension': r'\bfun\s+\w+\.(\w+)'
        }
        
        # Kotlin保留关键字
        self.kotlin_keywords = {
            'as', 'break', 'class', 'continue', 'do', 'else', 'false', 'for',
            'fun', 'if', 'in', 'interface', 'is', 'null', 'object', 'package',
            'return', 'super', 'this', 'throw', 'true', 'try', 'typealias',
            'typeof', 'val', 'var', 'when', 'while', 'by', 'catch', 'constructor',
            'delegate', 'dynamic', 'field', 'file', 'finally', 'get', 'import',
            'init', 'param', 'property', 'receiver', 'set', 'setparam', 'where',
            'actual', 'abstract', 'annotation', 'companion', 'const', 'crossinline',
            'data', 'enum', 'expect', 'external', 'final', 'infix', 'inline',
            'inner', 'internal', 'lateinit', 'noinline', 'open', 'operator',
            'out', 'override', 'private', 'protected', 'public', 'reified',
            'sealed', 'suspend', 'tailrec', 'vararg'
        }
    
    def extract_symbols(self, file_path: str) -> List[str]:
        """提取Kotlin文件中的符号"""
        symbols = []
        
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()
            
            # 移除注释和字符串
            content = self._remove_comments_and_strings(content)
            
            # 提取各种类型的符号
            for symbol_type, pattern in self.patterns.items():
                matches = re.findall(pattern, content)
                for match in matches:
                    if isinstance(match, tuple):
                        symbol = match[0] if match[0] else None
                    else:
                        symbol = match
                    
                    if symbol and self._is_valid_kotlin_symbol(symbol):
                        symbols.append(symbol)
            
            # 去重
            symbols = list(set(symbols))
            
        except Exception as e:
            self.logger.log_error(f"提取Kotlin符号失败: {file_path}, 错误: {str(e)}")
        
        return symbols
    
    def _remove_comments_and_strings(self, content: str) -> str:
        """移除注释和字符串，避免误提取"""
        # 移除多行注释
        content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL)
        # 移除单行注释
        content = re.sub(r'//.*?$', '', content, flags=re.MULTILINE)
        # 移除字符串
        content = re.sub(r'"(?:[^"\\]|\\.)*"', '""', content)
        content = re.sub(r"'(?:[^'\\]|\\.)*'", "''", content)
        # 移除模板字符串
        content = re.sub(r'\$\{[^}]*\}', '', content)
        
        return content
    
    def _is_valid_kotlin_symbol(self, symbol: str) -> bool:
        """检查是否是有效的Kotlin符号"""
        # 排除Kotlin关键字
        if symbol in self.kotlin_keywords:
            return False
        
        # 排除Android系统类前缀
        android_prefixes = ['android', 'androidx', 'com.google', 'java', 'javax', 'kotlin']
        for prefix in android_prefixes:
            if symbol.startswith(prefix):
                return False
        
        # 排除全大写（可能是常量）
        if symbol.isupper() and len(symbol) > 2:
            return False
        
        # 排除数字开头
        if symbol[0].isdigit():
            return False
        
        # 排除下划线开头（内部使用）
        if symbol.startswith('_'):
            return False
        
        # 排除特殊的Android生命周期方法
        lifecycle_methods = {
            'onCreate', 'onStart', 'onResume', 'onPause', 'onStop', 'onDestroy',
            'onRestart', 'onSaveInstanceState', 'onRestoreInstanceState',
            'onCreateView', 'onViewCreated', 'onActivityCreated', 'onAttach',
            'onDetach', 'onDestroyView', 'onConfigurationChanged',
            'onRequestPermissionsResult', 'onActivityResult'
        }
        if symbol in lifecycle_methods:
            return False
        
        # 排除Android资源相关的符号（R类）
        if symbol == 'R' or symbol.startswith('R.'):
            return False
        
        return self.config.is_symbol_safe_to_obfuscate(symbol, 'kotlin', '')[0]
    
    def obfuscate_file(self, file_path: str, symbol_map: Dict[str, str]):
        """混淆Kotlin文件"""
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()
            
            original_content = content
            
            # 识别保护区域
            protected_regions = self._identify_protected_regions(content)
            
            # 按符号长度排序，优先替换长符号
            sorted_symbols = sorted(symbol_map.items(), key=lambda x: len(x[0]), reverse=True)
            
            for original, obfuscated in sorted_symbols:
                # 使用词边界确保精确替换
                pattern = r'\b' + re.escape(original) + r'\b'
                # 使用安全替换
                content = self._safe_replace(content, pattern, obfuscated, protected_regions)
            
            # 检查是否有变化
            if content != original_content:
                if not self.config.dry_run:
                    with open(file_path, 'w', encoding='utf-8') as f:
                        f.write(content)
                
                self.logger.log_file_processing(file_path, "已混淆")
                
                # 检查是否需要重命名文件
                self._rename_file_if_needed(file_path, symbol_map)
            else:
                self.logger.log_file_processing(file_path, "无需混淆")
                
        except Exception as e:
            self.logger.log_error(f"混淆Kotlin文件失败: {file_path}, 错误: {str(e)}")
    
    def _identify_protected_regions(self, content: str) -> List[Tuple[int, int]]:
        """识别不应被混淆的区域"""
        regions = []
        
        # 多行注释 /* ... */
        for match in re.finditer(r'/\*.*?\*/', content, re.DOTALL):
            regions.append((match.start(), match.end()))
        
        # 单行注释 //
        for match in re.finditer(r'//.*?$', content, re.MULTILINE):
            regions.append((match.start(), match.end()))
        
        # 字符串 "..." 和 """..."""
        for match in re.finditer(r'""".*?"""|"(?:[^"\\]|\\.)*"', content, re.DOTALL):
            regions.append((match.start(), match.end()))
        
        # 字符串 '...'
        for match in re.finditer(r"'(?:[^'\\]|\\.)*'", content):
            regions.append((match.start(), match.end()))
        
        # 注解 @...
        for match in re.finditer(r'@\w+(?:\([^)]*\))?', content):
            regions.append((match.start(), match.end()))
        
        # Android资源引用 R.id.xxx, R.layout.xxx等
        for match in re.finditer(r'R\.\w+\.\w+', content):
            regions.append((match.start(), match.end()))
            
        return sorted(regions)
    
    def _safe_replace(self, content: str, pattern: str, replacement: str, protected_regions: List[Tuple[int, int]]) -> str:
        """安全替换，跳过保护区域"""
        result = []
        last_pos = 0
        
        for match in re.finditer(pattern, content):
            # 检查是否在保护区域内
            in_protected = False
            for start, end in protected_regions:
                if start <= match.start() < end:
                    in_protected = True
                    break
            
            if not in_protected:
                # 添加匹配前的内容
                result.append(content[last_pos:match.start()])
                # 添加替换内容
                result.append(replacement)
                last_pos = match.end()
        
        # 添加剩余内容
        result.append(content[last_pos:])
        return ''.join(result)
    
    def extract_class_symbols(self, content: str) -> List[str]:
        """提取类符号"""
        classes = []
        patterns = [
            r'\bclass\s+(\w+)',
            r'\bdata\s+class\s+(\w+)',
            r'\bsealed\s+class\s+(\w+)',
            r'\benum\s+class\s+(\w+)'
        ]
        for pattern in patterns:
            matches = re.findall(pattern, content)
            for match in matches:
                if self._is_valid_kotlin_symbol(match):
                    classes.append(match)
        return classes
    
    def extract_function_symbols(self, content: str) -> List[str]:
        """提取函数符号"""
        functions = []
        pattern = r'\bfun\s+(\w+)'
        matches = re.findall(pattern, content)
        for match in matches:
            if self._is_valid_kotlin_symbol(match):
                functions.append(match)
        return functions
    
    def extract_property_symbols(self, content: str) -> List[str]:
        """提取属性符号"""
        properties = []
        patterns = [
            r'\bval\s+(\w+)',
            r'\bvar\s+(\w+)'
        ]
        for pattern in patterns:
            matches = re.findall(pattern, content)
            for match in matches:
                if self._is_valid_kotlin_symbol(match):
                    properties.append(match)
        return properties
    
    def _rename_file_if_needed(self, file_path: str, symbol_map: Dict[str, str]):
        """如果文件中的主类被混淆，重命名文件"""
        try:
            import os
            from pathlib import Path
            
            file_name = Path(file_path).stem
            
            # 检查文件名是否在符号映射中
            if file_name in symbol_map:
                new_name = symbol_map[file_name]
                new_path = file_path.replace(f"{file_name}.kt", f"{new_name}.kt")
                
                if not self.config.dry_run:
                    os.rename(file_path, new_path)
                    self.logger.log_operation("文件重命名", f"{file_name}.kt -> {new_name}.kt")
                    
                    # 更新所有引用此文件的import语句
                    self._update_imports_in_project(file_name, new_name)
                    
        except Exception as e:
            self.logger.log_error(f"重命名文件失败: {file_path}, 错误: {str(e)}")
    
    def _update_imports_in_project(self, old_class_name: str, new_class_name: str):
        """更新项目中所有对该类的import引用"""
        try:
            import os
            from pathlib import Path
            
            # 获取项目根目录
            project_path = Path(self.config.project_path)
            
            # 遍历所有Kotlin文件
            for kt_file in project_path.rglob("*.kt"):
                try:
                    with open(kt_file, 'r', encoding='utf-8') as f:
                        content = f.read()
                    
                    original_content = content
                    
                    # 更新import语句
                    # 匹配类似: import com.example.testapp.MainActivity
                    import_pattern = rf'(import\s+[\w.]+\.){old_class_name}\b'
                    content = re.sub(import_pattern, rf'\1{new_class_name}', content)
                    
                    # 如果有变化，写回文件
                    if content != original_content and not self.config.dry_run:
                        with open(kt_file, 'w', encoding='utf-8') as f:
                            f.write(content)
                        self.logger.log_file_processing(str(kt_file), f"更新import: {old_class_name} -> {new_class_name}")
                        
                except Exception as e:
                    self.logger.log_error(f"更新import失败: {kt_file}, 错误: {str(e)}")
                    
        except Exception as e:
            self.logger.log_error(f"更新项目import失败: {str(e)}")
    
    def update_resource_ids_in_code(self, file_path: str, resource_id_map: Dict[str, str]):
        """更新Kotlin代码中的资源ID引用"""
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()
            
            original_content = content
            
            # 替换R.id.xxx引用
            for original_id, new_id in resource_id_map.items():
                # 将snake_case的资源ID转换为对应的代码中可能的形式
                # 资源ID在布局文件中是 snake_case，但在代码中通常保持原样
                
                # 替换 R.id.xxx 格式
                pattern = rf'\bR\.id\.{re.escape(original_id)}\b'
                replacement = f'R.id.{new_id}'
                content = re.sub(pattern, replacement, content)
            
            # 如果有变化，写回文件
            if content != original_content and not self.config.dry_run:
                with open(file_path, 'w', encoding='utf-8') as f:
                    f.write(content)
                self.logger.log_file_processing(file_path, f"更新了资源ID引用")
                
        except Exception as e:
            self.logger.log_error(f"更新资源ID失败: {file_path}, 错误: {str(e)}")
    
    def update_android_manifest(self, symbol_map: Dict[str, str]):
        """更新AndroidManifest.xml中的Activity声明"""
        try:
            import os
            from pathlib import Path
            
            project_path = Path(self.config.project_path)
            
            # 查找AndroidManifest.xml
            for manifest_file in project_path.rglob("AndroidManifest.xml"):
                try:
                    with open(manifest_file, 'r', encoding='utf-8') as f:
                        content = f.read()
                    
                    original_content = content
                    
                    # 更新Activity声明
                    for old_class, new_class in symbol_map.items():
                        # 匹配 android:name=".MainActivity"
                        old_pattern = f'android:name=".{old_class}"'
                        new_pattern = f'android:name=".{new_class}"'
                        if old_pattern in content:
                            content = content.replace(old_pattern, new_pattern)
                        
                        # 匹配完整路径 android:name="com.example.testapp.MainActivity"  
                        full_old = f'android:name="com.example.testapp.{old_class}"'
                        full_new = f'android:name="com.example.testapp.{new_class}"'
                        if full_old in content:
                            content = content.replace(full_old, full_new)
                    
                    # 写回文件
                    if content != original_content and not self.config.dry_run:
                        with open(manifest_file, 'w', encoding='utf-8') as f:
                            f.write(content)
                        self.logger.log_file_processing(str(manifest_file), "已更新Activity声明")
                        
                except Exception as e:
                    self.logger.log_error(f"更新AndroidManifest失败: {manifest_file}, 错误: {str(e)}")
                    
        except Exception as e:
            self.logger.log_error(f"更新AndroidManifest失败: {str(e)}")